﻿using Exiled.API.Features;
using Exiled.API.Features.Pools;
using Exiled.Events.EventArgs.Interfaces;
using Exiled.Events.EventArgs.Player;
using Exiled.Events.Features;
using MEC;
using RgCore.API;
using RgCore.Events;
using RgCore.PlayerHud;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CustomCharacter.Ability
{
    public struct AbilityInfo
    {
        public Ability Ability;
        public CooldownStatus<Player> CooldownStatus;
        public Dictionary<string, object> Datas;

        public AbilityInfo(Ability ability, CooldownStatus<Player> cooldownStatus)
        {
            Ability = ability;
            CooldownStatus = cooldownStatus;
            Datas = DictionaryPool<string, object>.Pool.Get();
            if (ability.DataArgs != null)
            {
                foreach (var key in ability.DataArgs)
                {
                    Datas.Add(key.Key, key.Value);
                }
            }
        }
    }

    public static class AbilityManager
    {
        public static void Initialize()
        {
            Exiled.Events.Handlers.Player.Left.Subscribe(Player_Left);
        }

        private static IEnumerator<float> Player_Left(LeftEventArgs ev)
        {
            ev.Player?.RevokeAbility();
            yield break;
        }

        public static IEnumerator<float> Update()
        {
            while (true)
            {
                for (int i = 0; i < AbilityInvalid.Count; i++)
                {
                    var key = AbilityInvalid.ElementAt(i);
                    if (key.Value < DateTime.Now)
                    {
                        AbilityInvalid.Remove(key.Key);
                        i--;
                    }
                }
                for (int i = 0; i < Abilities.Count; i++)
                {
                    var cc = Abilities.ElementAt(i);
                    try
                    {
                        for (int j = 0; j < cc.Value.Count; j++)
                        {
                            var ai = cc.Value[j];
                            if (ai.Ability is AttributeAbility a)
                            {
                                if (a.Update)
                                {
                                    cc.Key.Trigger(ai.Ability, out _, AbilityTrigger.Update);
                                }
                            }
                            else if (ai.CooldownStatus.Status == CooldownStatus<Player>.CdStatus.LongActivating || ai.CooldownStatus.Status == CooldownStatus<Player>.CdStatus.Activating)
                            {
                                cc.Key.Trigger(ai.Ability, out _, AbilityTrigger.Active_Update);
                            }
                            else if (!ai.CooldownStatus.DeactivationFinished)
                            {
                                ai.CooldownStatus.DeactivationFinished = true;

                                Abilities[cc.Key][j] = ai;
                                cc.Key.Trigger(ai.Ability, out _, AbilityTrigger.Deactive);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                        continue;
                    }
                }
                yield return Timing.WaitForSeconds(1.0f);
            }
        }

        public static bool Trigger(this Player player, Ability ability, out string response, AbilityTrigger trigger, IExiledEvent e = null)
        {
            response = "";
            if (!player.IsAlive)
                return false;
            if (AbilityInvalid.TryGetValue(player, out var time) && DateTime.Now < time)
                return false;
            //Log.Info($"Try Run: {ability.AbilityId} | {e?.GetType().Name ?? "null"} | {trigger}");
            try
            {
                if (!Abilities.TryGetValue(player, out var abilities))
                {
                    response = "该技能不在该角色可用列表内";
                    return false;
                }
                int index = abilities.FindIndex(x => x.Ability.AbilityId == ability.AbilityId);
                if (index == -1)
                {
                    response = "该技能不在该角色可用列表内";
                    return false;
                }
                AbilityInfo ai = abilities[index];
                AbilityOutput result;
                bool res = false;
                //Attribute
                if (ability is AttributeAbility aa)
                {
                    switch (trigger)
                    {
                        case AbilityTrigger.Event:
                        case AbilityTrigger.Update:
                            result = ability.Function(player, ref ai, e, trigger);
                            response = result.Response;
                            return result.Success;
                        default:
                            return false;
                    }
                }
                //Offensive & Passive
                if (trigger == AbilityTrigger.Update)
                    return false;
                if (trigger == AbilityTrigger.Active)
                {
                    if (ability.Type == AbilityType.Passive || !ai.CooldownStatus.Condition(player, out response))
                        return false;
                    if (ai.CooldownStatus.Status == CooldownStatus<Player>.CdStatus.Cooling)
                    {
                        response = "技能冷却中";
                        return false;
                    }
                }
                if (trigger == AbilityTrigger.Event && (ability.Type == AbilityType.Offensive || ai.CooldownStatus.Status == CooldownStatus<Player>.CdStatus.Cooling || !ai.CooldownStatus.Condition(player, out response)))
                {
                    return false;
                }
                result = ability.Function(player, ref ai, e, trigger);
                response = result.Response;
                res = result.Success;
                if (res && trigger != AbilityTrigger.Deactive)
                {
                    if (result.SetValueMode)
                    {
                        if (result.ActivationChange <= 0)
                        {
                            ai.CooldownStatus.Deactivate();
                            if (!ai.CooldownStatus.DeactivationFinished)
                            {
                                ai.CooldownStatus.DeactivationFinished = true;
                                abilities[index] = ai;
                                player.Trigger(ability, out _, AbilityTrigger.Deactive, null);
                            }
                        }
                        else
                            ai.CooldownStatus.Activate((uint)result.ActivationChange);
                    }
                    else if (result.ActivationChange != 0)
                    {
                        ai.CooldownStatus.AddActivateDuration(result.ActivationChange);
                    }
                    if (!ai.CooldownStatus.Status.HasFlag(CooldownStatus<Player>.CdStatus.Activating))
                    {
                        ai.CooldownStatus.StartCD();
                    }
                    if (trigger == AbilityTrigger.Event && ability.Type == AbilityType.Passive)
                    {
                        int num = abilities.Where(x => x.Ability.Type == AbilityType.Passive).ToList().FindIndex(x => x.Ability == ability);
                        player.SendRawMsg("passive", $"<color=yellow>〔</color><color=#48D1CC>被动技能[{++num}]</color> 已触发<color=yellow>〕</color>", 2, ScreenLocation.Bottom, true);
                    }
                }
                abilities[index] = ai;
                return res;
            }
            catch (Exception ex)
            {
                Log.Error(ex);
            }
            return false;
        }

        private static List<IExiledEvent> RegisteredEvents = new List<IExiledEvent>();
        public static List<Ability> RegisteredAbilities = new List<Ability>();
        public static Dictionary<Player, List<AbilityInfo>> Abilities = new Dictionary<Player, List<AbilityInfo>>();
        public static Dictionary<Player, DateTime> AbilityInvalid = new Dictionary<Player, DateTime>();

        public static void RegisterAbility(this Ability ability)
        {
            if (RegisteredAbilities.Contains(ability))
            {
                return;
            }
            RegisteredAbilities.RemoveAll(x => x.AbilityId == ability.AbilityId);
            if (ability.ToRegisteredEvent != null)
            {
                foreach (var ev in ability.ToRegisteredEvent)
                {
                    if (RegisteredEvents.Contains(ev))
                        continue;
                    Type[] unparametered = new Type[] { typeof(Event) };
                    if ((ev.GetType().IsGenericType && ev.GetType().GetGenericTypeDefinition() == typeof(Event<>)) || ev.GetType().BaseType.GetGenericTypeDefinition() == typeof(CustomEvent<>))
                    {
                        var argu = ev.GetType().GenericTypeArguments.FirstOrDefault() ?? ev.GetType().BaseType.GenericTypeArguments.FirstOrDefault();
                        if (argu == null)
                            continue;
                        var x = typeof(CustomEventHandler<>).MakeGenericType(argu);
                        var method = ev.GetType().GetMethod(nameof(Event.Subscribe), new Type[] { typeof(CustomEventHandler<>).MakeGenericType(argu) });
                        var type = method.GetParameters().First().ParameterType;
                        var dele = Delegate.CreateDelegate(type, typeof(AbilityManager).GetMethod(nameof(EventCatcher)));
                        method.Invoke(ev, new object[] { dele });
                        RegisteredEvents.Add(ev);
                    }
                    else if (unparametered.Contains(ev.GetType()))
                    {
                        ev.GetType().GetMethod(nameof(Event.Subscribe), new Type[] { typeof(CustomEventHandler) })
                             .Invoke(ev, new object[] { new CustomEventHandler(EventCatcherNoArgs) });
                        RegisteredEvents.Add(ev);
                    }
                }
            }
            RegisteredAbilities.Add(ability);
        }

        public static void GrantAbility(this Player player, Ability ability)
        {
            if (!Abilities.TryGetValue(player, out List<AbilityInfo> abilities))
            {
                abilities = ListPool<AbilityInfo>.Pool.Get();
                Abilities.Add(player, abilities);
            }
            abilities.Add(new AbilityInfo(ability, new CooldownStatus<Player>(ability.Cooldown, ability.Condition)));
            ability.RegisterAbility();
        }

        public static void RevokeAbility(this Player player, Ability ability = null)
        {
            if (!Abilities.TryGetValue(player, out List<AbilityInfo> abilities))
            {
                return;
            }
            for (int i = 0; i < abilities.Count; i++)
            {
                var ab = abilities[i];
                if (ab.Ability == ability || ability == null)
                {
                    abilities.RemoveAt(i);
                    DictionaryPool<string, object>.Pool.Return(ab.Datas);
                    i--;
                }
            }
            if (abilities.Count == 0)
            {
                ListPool<AbilityInfo>.Pool.Return(abilities);
                Abilities.Remove(player);
            }
        }

        public static AbilityInfo GetAbility(this Player player, string ability_id)
        {
            if (Abilities.TryGetValue(player, out List<AbilityInfo> abilities))
            {
                return abilities.FirstOrDefault(x => x.Ability.AbilityId == ability_id);
            }
            return default;
        }

        public static bool HasAbility(this Player player, string ability_id)
        {
            var result = GetAbility(player, ability_id);
            return result.Ability != null;
        }

        public static void SetInvalid(this Player player, uint duration)
        {
            if (!AbilityInvalid.ContainsKey(player))
            {
                AbilityInvalid.Add(player, DateTime.Now);
            }
            AbilityInvalid[player] = AbilityInvalid[player].AddSeconds(duration);
        }

        public static void EventCatcher(object ev)
        {
            if (ev is IExiledEvent e)
            {
                for (int i = 0; i < Abilities.Count; i++)
                {
                    try
                    {
                        var cc = Abilities.ElementAt(i);
                        for (int j = 0; j < cc.Value.Count; j++)
                        {
                            AbilityInfo ai = cc.Value[j];
                            if (ai.Ability.Type == AbilityType.Attribute 
                                || (ai.Ability.Type == AbilityType.Passive 
                                && ai.CooldownStatus.Status != CooldownStatus<Player>.CdStatus.Cooling) 
                                &&  e == null || (ai.Ability.ToRegisteredEvent != null && ai.Ability.ToRegisteredEvent.Any(x =>
                            (x.GetType().IsGenericType && x.GetType().GetGenericArguments().Contains(e.GetType())) ||
                            (x.GetType().BaseType.IsGenericType && x.GetType().BaseType.GetGenericTypeDefinition() == typeof(CustomEvent<>)
                            && x.GetType().BaseType.GetGenericArguments().Contains(e.GetType())))))
                            {
                                cc.Key.Trigger(ai.Ability, out _, AbilityTrigger.Event, e);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Info(ex);
                        continue;
                    }
                }
            }
        }

        public static void EventCatcherNoArgs()
        {
            EventCatcher(null);
        }
    }
}